﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace Reactor.Collections
{
    public class FilteringCollection<T, TM> : AdaptingCollection<T, TM>
    {
        public FilteringCollection(Func<Lazy<T, TM>, bool> filter) : base(e => e.Where(filter)) {}
    }

    public class OrderingCollection<T, TM> : AdaptingCollection<T, TM>
    {
        public OrderingCollection(Func<Lazy<T, TM>, object> keySelector, bool descending = false)
            : base(e => descending ? e.OrderByDescending(keySelector) : e.OrderBy(keySelector))
        {
        }
    }

    public interface IOrderMetadata
    {
        [DefaultValue(Int32.MaxValue)]
        int Order { get; }
    }

    public class AdaptingCollection<T> : AdaptingCollection<T, IDictionary<string, object>>
    {
        public AdaptingCollection(Func<IEnumerable<Lazy<T, IDictionary<string, object>>>,
                                       IEnumerable<Lazy<T, IDictionary<string, object>>>> adaptor)
            : base(adaptor)
        {
        }
    }

    public class AdaptingCollection<T, TM> : ICollection<Lazy<T, TM>>, INotifyCollectionChanged
    {
        private readonly List<Lazy<T, TM>> _allItems = new List<Lazy<T, TM>>();
        private readonly Func<IEnumerable<Lazy<T, TM>>, IEnumerable<Lazy<T, TM>>> _adaptor;
        private List<Lazy<T, TM>> _adaptedItems;

        public AdaptingCollection()
            : this(null)
        {
        }

        public AdaptingCollection(Func<IEnumerable<Lazy<T, TM>>, IEnumerable<Lazy<T, TM>>> adaptor)
        {
            _adaptor = adaptor;
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public void ReapplyAdaptor()
        {
            if (_adaptedItems == null) return;

            _adaptedItems = null;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        protected virtual IEnumerable<Lazy<T, TM>> Adapt(IEnumerable<Lazy<T, TM>> collection)
        {
            if (_adaptor != null)
            {
                return _adaptor.Invoke(collection);
            }

            return collection;
        }

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;

            if (collectionChanged != null)
            {
                collectionChanged.Invoke(this, e);
            }
        }

        private List<Lazy<T, TM>> AdaptedItems
        {
            get { return _adaptedItems ?? (_adaptedItems = Adapt(_allItems).ToList()); }
        }

        #region ICollection Implementation
        // Accessors work directly against adapted collection
        public bool Contains(Lazy<T, TM> item)
        {
            return AdaptedItems.Contains(item);
        }

        public void CopyTo(Lazy<T, TM>[] array, int arrayIndex)
        {
            AdaptedItems.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return AdaptedItems.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public IEnumerator<Lazy<T, TM>> GetEnumerator()
        {
            return AdaptedItems.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        // Mutation methods work against complete collection
        // and then force a reset of the adapted collection
        public void Add(Lazy<T, TM> item)
        {
            _allItems.Add(item);
            ReapplyAdaptor();
        }

        public void Clear()
        {
            _allItems.Clear();
            ReapplyAdaptor();
        }

        public bool Remove(Lazy<T, TM> item)
        {
            bool removed = _allItems.Remove(item);
            ReapplyAdaptor();
            return removed;
        }
        #endregion
    }
}
